07 | Nacos体系架构:什么是服务治理?

讲述:姚秋辰

时长20:14大小18.54M

你好,我是姚秋辰。
从今天开始,我们的课程就正式进入 Spring Cloud 环节了。我先带你学习微服务架构中一个最重要的原理概念:服务治理。在概念讲解之后,我还会向你介绍 Nacos 服务注册中心的体系结构。通过这节课的学习,你可以了解微服务的完整生命周期,知晓服务注册中心在微服务架构中发挥了什么作用,这些内容能让你对 Nacos 的体系架构有一个比较全面的认识。
首先,让我通过一个例子告诉你服务治理解决了什么问题。
我的系统包含两个微服务(服务 A 和服务 B),每一个微服务有 10 个虚拟节点,两个服务组成了一个 20 台虚拟机的微服务集群。如果此时微服务 A 想要调用微服务 B,我们怎么来发起这个调用呢?
一种通用做法是:在服务 A 的配置文件中添加一个指向服务 B 的地址,但这个地址并不直接指向任何一台服务 B 集群中的节点,而是指向一个 VIP(虚拟 IP 地址)或者是一个网关。这个 VIP 或网关背后维护了 B 集群的服务节点列表,VIP 层通过负载均衡策略再将请求转到后面配置的某一台服务器。我画了一幅图来描述这个服务调用过程。
从上面的图中我们可以看出,服务 A 与服务 B 之间互相不直接通信,服务调用完全依靠 VIP 作为中间人来完成。我们如果想要为服务集群扩容或缩容,必须将服务器配置到对应的 VIP 地址上。如果你的应用是一个由数百个微服务组成的大型应用,光是管理这些 VIP Pool 的人力成本就够网络运维团队喝上一壶了。
那在微服务架构中,怎么才能实现一种简单可靠的远程服务调用,不让 VIP 中间商赚差价呢?这就要说到我们的服务治理理论了。

服务治理初探

如果我们要解决中间商赚差价的问题,那么最好的办法就是让双方直连。因此,服务治理要解决的首要任务就是服务注册服务发现,通过这两项技术,我们就能让微服务之间发起面对面的直接调用。
那么服务 A 怎么知道服务 B 中每台机器的地址呢?为了让服务 A 拿到服务 B 的机器清单,我们需要搭建一个中心化的服务注册中心,服务 B 只要将自己的信息添加到注册中心里,服务 A 就能够从注册中心获取到服务 B 的所有节点列表。我画了一张图来帮助你更好地理解这个过程。
从上图中的步骤中我们可以看出,首先,服务 B 集群向注册中心发起了注册,将自己的地址信息上报到注册中心,这个过程就是服务注册。接下来,每隔一段时间,服务 A 就会从服务中心获取服务 B 集群的服务列表,或者由服务中心将服务列表的变动推送给服务 A,这个过程叫做服务发现;最后,服务 A 根据本地负载均衡策略,从服务列表中选取某一个服务 B 的节点,发起服务调用。
在这个过程中,注册中心的角色是一个中心化的信息管理者,所有的微服务节点在启动后都会将自己的地址信息添加到注册中心。在服务注册的过程中,有两个关键信息是最为重要的,我把它们列在了这里。
服务名称:服务名称通常默认是 spring.application.name 属性,在服务注册过程中我们必须将应用服务名上报到注册中心,这样其他服务才能根据服务名称找到对应的服务节点列表;
地址信息:包括服务节点的 IP 地址和端口。
通过上面这两个信息,调用方就能精准定位到目标微服务。除此之外,服务注册请求中还包含一些额外的注册信息,我将在 Nacos 的实战环节为你详细讲解这些注册参数。
通过服务注册和服务发现,我们已经能够实现端到端的服务调用链路,但这个方案似乎还并不完善,因为它缺少了异常容错的机制。
如果服务 B 集群因为未知的网络故障导致无法响应服务,这时候服务 A 向服务 B 发起了服务调用,就会发生超时或者服务无响应的异常情况。那我们如何在服务治理方案中规避这类问题呢?
业界通用的解决方案是“heathcheck”或者“heartbeat”,又叫“服务探活”或“心跳检查”。注册中心可以通过这种机制来标记异常服务,这样一来,Client 端在发送服务请求的时候就能避开异常节点。
我将这些异常处理的步骤添加到了服务注册流程中,并画了一个完整的微服务生命周期的图,你可以参考一下。
看了图片,你可能会问,怎么还有一个“服务剔除”呢?它是怎么实现的?
先说一个大前提。所有的服务都要在注册中心进行注册,而且每个节点都需要每隔一段时间向注册中心同步自己当前的状态,我们很形象地称这个过程为 heartbeat(心跳)。
如果节点持续发送心跳信息,则一切正常,服务可以被发现;如果注册中心在一段时间内没有收到 Client 的心跳包,注册中心就会将这个节点标记为下线状态,进而将该服务从服务列表中剔除。
这里我再补充一句,我们上面说的“服务剔除”是由注册中心主导的“被动下线”场景。除此之外还有一类服务“主动下线”的场景,也就是当服务节点关闭或者重启的时候,通过发送一条“服务下线”指令给到注册中心,将当前节点标记为下线状态。
到这里,相信你已经完全理解了微服务生命周期各个状态间的流转,也知道了服务注册中心在微服务生命周期中扮演了什么角色。
接下来,我们来了解 Spring Cloud 中的服务注册中心 Nacos。

Nacos 体系架构

Nacos 有三个核心知识点:领域模型、数据模型和基本架构,这是我们整体把握 Nacos 架构的关键。下面我们来依次看看。

领域模型

Nacos 领域模型描述了服务与实例之间的边界和层级关系。Nacos 的服务领域模型是以“服务”为维度构建起来的,这个服务并不是指集群中的单个服务器,而是指微服务的服务名。
“服务”是 Nacos 中位于最上层的概念,在服务之下,还有集群和实例的概念。为了方便你理解这三者的层级关系,我画了一张图,你可以参考一下。
从上面的图中你可以看出,Nacos 的服务领域模型从上到下分为了服务、集群和实例三层,我分别介绍一下这三个层次所包含的重要数据内容。
服务
在服务这个层级上我们可以配置元数据和服务保护阈值等信息。服务阈值是一个 0~1 之间的数字,当服务的健康实例数与总实例的比例小于这个阈值的时候,说明能提供服务的机器已经没多少了。这时候 Nacos 会开启服务保护模式,不再主动剔除服务实例,同时还会将不健康的实例也返回给消费者。尽管这样做可能造成请求失败,但间接保证了最低限度的服务可用性。
集群
一个服务由很多服务实例组成,在每个服务实例启动的时候,我们可以设置它所属的集群,在集群这个层级上,我们也可以配置元数据。除此之外,我们还可以为持久化节点设置健康检查模式。
所谓持久化节点,是一种会保存到 Nacos 服务端的实例,即便该实例的客户端进程没有在运行,实例也不会被服务端删除,只不过 Nacos 会将这个持久化节点状态标记为不健康,Nacos 可以采用一种“主动探活”的方式来对持久化节点做健康检查。
除了持久化节点以外,大部分服务节点在 Nacos 中以“临时节点”的方式存在,它是默认的服务注册方式,从名字中我们就可以看出,这种节点不会被持久化保存在 Nacos 服务器,临时节点通过主动发送 heartbeat 请求向服务器报送自己的状态。
实例
这里所说的实例就是指服务节点,我们可以在 Nacos 控制台查看每个实例的 IP 地址和端口、编辑实例的元数据信息、修改它的上线 / 下线状态或者配置路由权重等等。
你会发现,在这三个层级上都有“元数据”这一数据结构,你可以把它理解为一组包含了服务描述信息(如服务版本等)和自定义标签的数据集合。Client 端通过服务发现技术可以获取到每个服务实例的元数据,你可以将自定义的属性加入到元数据并在 Client 端实现某些定制化的业务场景。
了解了领域模型之后,你知道服务调用的发起方是如何定位到领域模型中的服务实例的吗?这就要说起 Nacos 的数据模型了。

数据模型

Nacos 的数据模型有三个层次结构,分别是 Namespace、Group 和 Service/DataId,我画了一幅图,帮你理解这三个层次之间的包含关系:
从上图中你可以看出,Namespace、Group 和 Service/DataId 是一个依次包含的结构,我分别对每一层做一个简单介绍。
Namespace:即命名空间,它是最顶层的数据结构,我们可以用它来区分开发环境、生产环境等不同环境。默认情况下,所有服务都部署到一个叫做“public”的公共命名空间;
Group:在命名空间之下有一个分组结构,默认情况下所有微服务都属于“DEFAULT_GROUP”这个分组,不同分组间的微服务是相互隔离的;
Service/DataID:在 Group 分组之下,就是具体的微服务了,比如订单服务、商品服务等等。
通过 Namespace + Group + Service/DataID,我们就可以精准定位到一个具体的微服务。比如,我想调用生产环境下 A 分组的订单服务,那么对应的服务寻址的 Key 就是类似 Production.A.orderService 的组合。
了解了 Nacos 的数据模型之后,我再来带你看一下 Nacos 的基本架构,这样你就对 Nacos 的功能模块有一个更全面的认识。

Nacos 基本架构

Nacos 的核心功能有两个,一个是 Naming Service,也就我们用来做服务发现的模块;另一个是 Config Service,用来提供配置项管理、动态更新配置和元数据的功能,关于配置管理的内容我会放到课程中的配置管理阶段为你详细讲解。
我这里用一张 Nacos 社区的基本架构图来作为示例,带你看一下 Nacos 在功能模块层面的基本架构。
从上面的图中你可以看出,Provider APP 和 Consumer APP 通过 Open API 和 Nacos 服务器的核心模块进行通信。这里的 Open API 是一组对外暴露的 RESTful 风格的 HTTP 接口。如果你对 Open API 里具体的接口感兴趣,可以从Nacos 官方网站获取更多的关于 Open API 的详细信息。
在 Nacos 和核心模块里,Naming Service 提供了将对象和实体的“名字”映射到元数据的功能,这是服务发现的基础功能之一。例如,我想要调用 OrderService,我手里有这个服务的 Namespace 和 Group 信息,那么我就可以通过 Naming Service 定位到这个服务对应的实例列表。同理,如果我有一个 DNS 名称,同样可以借助 Naming Service 获取 DNS 背后配置的 IP 列表。以上两个场景就分别对应了服务发现和 DNS 功能,这两个场景都是 Naming Service 的核心场景。
Nacos 还有一个相当重要的模块:Nacos Core 模块。它可以提供一系列的平台基础功能,是支撑 Nacos 上层业务场景的基石。我挑选了几个 Nacos Core 中包含的重要功能,你可以看一下。
除了 Nacos Core 提供的这些功能以外,Nacos 还有一个“一致性协议”,用来确保 Nacos 集群中各个节点之间的数据一致性。Nacos 内部支持两种一致性协议,一种是侧重一致性的 Raft 协议,基于集群中选举出来的 Leader 节点进行数据写入;另一种是针对临时节点的 Distro 协议,它是一个侧重可用性(或最终一致性)的分布式一致性协议。
到这里,我们就完成了 Nacos 的基本架构部分的学习。

总结

现在,我们来回顾一下这节课的重点内容。今天我带你了解了服务治理所解决的问题。在这个问题解决的过程中,你自然建立起了对微服务生命周期各个状态的了解,你也能清楚地感受到服务注册中心 Nacos 的重要程度。为了让你更全面地认识 Nacos 的功能体系,我为你讲解了领域模型、数据模型和基础架构。
此外,我要给你一个小提示,虽然这节课我并没有深入介绍 Nacos 底层一致性协议的原理,但一致性协议是近年来面试中常问到的热点问题,我建议你借这个机会,去主动了解一些常见的经典协议。
在后面的课程中,我将带你搭建一个 Nacos 服务器集群,通过对 Spring Boot 项目的 Nacos 服务化改造,将实战项目里的本地方法调用改为微服务架构下的远程调用。通过实战环节的动手练习,你将对本节课的理论内容有更深的理解。

思考题

如果你正在开发的业务系统采用的是微服务架构,那么你如何实现远程服务调用呢?欢迎在留言区写下你的技术方案和技术选型,最好能再描述下背后的原理。
好啦,这节课就结束啦。欢迎你把这节课分享给更多对 Spring Cloud 感兴趣的朋友。我是姚秋辰,我们下节课再见!
分享给需要的人,Ta订阅后你可得20现金奖励
生成海报并分享

赞 2

提建议

上一篇
06 | 牛刀小试:如何搭建优惠券计算服务和用户服务?
下一篇
08 | 服务治理:Nacos集群环境搭建
 写留言

精选留言(8)

  • 起开我要掉渣了
    2021-12-28
    速更速更+++

    作者回复: 这位同学你先坐稳扶好,别急,男人嘛还是要慢~慢~来~ 太快不好

    车门已焊死,不学完你不能下车!

    共 2 条评论
    1
  • gevin
    2021-12-27
    老师,请教个问题。用nacos做服务注册时,如果有些服务时本地起的,有些是docker起的,那么默认情况下,docker起的服务,nacos中的注册地址是docker内部IP,本地起的服务,nacos中注册地址是本地服务器的IP,这就导致了本地服务访问不到docker服务的问题。
    对于这个问题,现在用的比较多的解决方案有哪些?
    展开

    作者回复: spring.cloud.nacos.discovery或者spring.cloud.inetutils下可以指定宿主机IP,我一般本地测试就用这个方法,再设置docker网络用host模式,这样就可以共用同一个物理机

    共 2 条评论
    1
  • Mars
    2021-12-30
    我们现在用的是eureka+apollo做的企业级的注册中心和配置中心。

    作者回复: apollo也很稳,国内用的挺广泛的,我自己感觉比spring cloud config好用

  • kimoti
    2021-12-28
    想请教老师为啥要保持一致性?

    作者回复: 比如说,一个妹子被劈腿了,她要跟渣男分手,分手这个事儿分两步,1)扇渣男一巴掌 2)吼一句:给老娘滚!

    这两步一气呵成缺一不可,如果你只删了一巴掌,渣男还一脸懵逼都不知道这是为啥。所以两件事儿都做完了,才达成目的,要做都做,要么都不做。这就是事务的一致性

    事务不一致的情况很常见,比如巴掌扇了,“滚”始终没有勇气喊出来,这种情况怎么办?这就是我在后面课程将要讲到的,如何用seata组件保证事务的一致性,同学敬请期待。

    共 3 条评论
    1
  • 暮雨yl晨曦
    2021-12-27
    老师这节课中提到了一致性协议,没有深入刨析,希望后续能有加餐,结合nacos深入讲解一下。

    作者回复: 我刚想案例黄老师的算法课来着哈哈,里面会有distro协议的讲解。我让编辑大大记一个,后面整体出完课程之后看怎么安排加餐

    共 6 条评论
  • 暮雨yl晨曦
    2021-12-27
    我们部门原先用的是gRpc,不好用。无论是调试还是写proto文件都很麻烦,我记得我第一次用这玩意,搞了好几天才跑通。现在使用的用dubbo居多。我们正在考虑要用SpringCloud Alibaba,我想要深入了解一下这块。正好看到现在的课程,就买来顺手学一下。
    展开

    作者回复: gRPC太难用了,protobuf坑很多,而且有时候自定义新属性的时候,新手很可能不知道要“追加”新属性,而是删除之前老的上线属性加塞一个新属性,造成各种线上问题。横向比较淘系的hsf还有阿里开源的dubbo,确实开发维护成本上来讲grpc有点坑

  • 李峰
    2021-12-27
    java.net.UnknownHostException: failed to resolve 'coupon-template-serv' after 2 queries
        at io.netty.resolver.dns.DnsResolveContext.finishResolve(DnsResolveContext.java:1013) ~[netty-resolver-dns-4.1.58.Final.jar:4.1.58.Final]
    我的三个服务都是起到windos的环境里面,老师保证错,这个coupon-template-serv域名他是在哪里解析成我的ip的?是需要配置什么吗?
    展开

    作者回复: 同学是启动的“1-服务治理和负载均衡Nacos+Loadbalancer”这个folder下的项目并且未做任何修改吗?还是自己本地做了一些修改的项目呢?可以把完整的报错栈打印出来看一下,我感觉是没有加Loadbalanced注解或者依赖项导致的问题

  • gevin
    2021-12-27
    远程服务调用,本质上都是RPC;但是,RESTful这种架构风格,统一了双方的通信契约,如,统一用http作为通信协议,URL均面向资源,用http方法映射为资源的CRUD,http code 反应API的语意,用json或xml做完数据的载体等,这让RESTful对开发人员更友好,也方便了异构系统等集成,所以,微服务架构,用RESTful架构风格设计API最合适
    展开

    作者回复: 我也比较喜欢http rest调用,国外公司用的会比较多。相比hsf, dubbo这类在国内比较流行的框架来说,另一个在国外流行的grpc真的是太难用了,定义protobuf就够喝上一壶。不过dubbo集成成本更低,丢一个api就能给上下游依赖方做远程调用